package cn.lyjuan.first.hadoop.demo.ch04;

import cn.lyjuan.base.util.RandomUtils;
import cn.lyjuan.first.hadoop.demo.enums.ChNameEnum;
import cn.lyjuan.first.hadoop.util.FileUtil;
import cn.lyjuan.first.hadoop.util.HDFSUtil;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Partitioner;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 对源数据，解析矩形长宽，按面积排序
 * 对正方形和长方形进行分区
 */
public class Ch04S02Rectangle {
    /**
     * 自定义任务类型
     */
    @Data
    @NoArgsConstructor
    public static class RectangleWritable implements WritableComparable<RectangleWritable> {
        private int length;
        private int width;

        public RectangleWritable(int length, int width) {
            this.length = length;
            this.width = width;
        }

        /**
         * 面积升序排序
         *
         * @param o
         * @return
         */
        @Override
        public int compareTo(RectangleWritable o) {
            // 返回小于0，当前对象(this)会排在比较对象(o)前面
            // 即默认升充排序
            // 这里要实现降充排序，所以反过来
            return o.length * o.width - this.length * this.width;
        }

        @Override
        public void write(DataOutput out) throws IOException {
            out.writeInt(this.length);
            out.writeInt(this.width);
        }

        @Override
        public void readFields(DataInput in) throws IOException {
            this.length = in.readInt();
            this.width = in.readInt();
        }
    }

    /**
     * 任务分区处理器
     */
    public static class AnglePartitioner extends Partitioner<RectangleWritable, NullWritable> {
        public static final int PARTITION_RECT = 0;// 长方形
        public static final int PARTITION_SQUARE = 1;// 正方形

        @Override
        public int getPartition(RectangleWritable rectangleWritable, NullWritable nullWritable, int i) {
            // 返回数据所处分区
            return rectangleWritable.getLength() == rectangleWritable.getWidth() ? PARTITION_SQUARE : PARTITION_RECT;
        }
    }

    /**
     * <p>
     * Map任务:<br />
     * 1. 解析数据<br/>
     * 2. 排序
     * </p>
     * <p>
     * 数据格式:<br/>
     * {length} {width}<br/>
     * 一行一条数据
     * </p>
     */
    public static class AngleMapper extends Mapper<LongWritable, Text, RectangleWritable, NullWritable> {
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            String[] source = value.toString().split(" ");
            RectangleWritable rect = new RectangleWritable(Integer.parseInt(source[0]), Integer.parseInt(source[1]));
            context.write(rect, NullWritable.get());
        }
    }

    /**
     * 任务结果数据：合并、排序<br/>
     * 输入格式：{length} {width}
     */
    public static class AngleReduce extends Reducer<RectangleWritable, NullWritable, IntWritable, IntWritable> {
        @Override
        protected void reduce(RectangleWritable key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
            context.write(new IntWritable(key.getLength()), new IntWritable(key.getWidth()));
        }
    }

    public static final Path OUT_PATH = FileUtil.remoteURIPath(ChNameEnum.CH04, Ch04S02Rectangle.class, "result");

    public static final List<Path> inFiles = new ArrayList<>();

    /**
     * 生成HDFS源数据文件
     */
    public static void loadInputFiles() {
        int fileCount = RandomUtils.randomInt(2);
        Path remoteDir = new Path(FileUtil.remotePath(ChNameEnum.CH04, Ch04S02Rectangle.class));
        HDFSUtil.del(remoteDir);// 清除旧数据
        Path path = null;
        for (int i = 0; i < fileCount; i++) {
            path = FileUtil.remoteURIPath(ChNameEnum.CH04, Ch04S02Rectangle.class, String.valueOf(i));
            inFiles.add(path);
            int row = RandomUtils.randomInt(2);
            StringBuilder sb = new StringBuilder();
            for (int j = 0; j < row; j++) {
                sb.append("\n").append(RandomUtils.randomInt(1)).append(" ").append(RandomUtils.randomInt(1));
            }
            sb.deleteCharAt(0);
            HDFSUtil.writeRemote(path, sb.toString());
            System.out.println("write remote file: " + path.toString());
        }
    }

    public static void main(String[] args) throws Exception {
        // 加载源文件
        loadInputFiles();

        Job job = new Job(HDFSUtil.conf(), "rectangle partition");
        job.setUser(HDFSUtil.USER);
        job.setJarByClass(Ch04S02Rectangle.class);
        job.setMapperClass(AngleMapper.class);
        /**
         * 设置分区信息
         */
        job.setNumReduceTasks(2);
        job.setPartitionerClass(AnglePartitioner.class);

//        job.setCombinerClass(AngleReduce.class);
        job.setReducerClass(AngleReduce.class);
        job.setOutputKeyClass(RectangleWritable.class);
        job.setOutputValueClass(NullWritable.class);
        for (Path i : inFiles)
            FileInputFormat.addInputPath(job, i);
        FileOutputFormat.setOutputPath(job, OUT_PATH);
        boolean isOk = job.waitForCompletion(true);
        System.out.println("result ==> " + isOk);
        if (!isOk) return;
        // 成功时生成结果文件，报错时不生成任何文件
        FileStatus[] results = HDFSUtil.ls(OUT_PATH);
        for (FileStatus f : results) {
            System.out.println("====== " + f.getPath().getName() + " ======");
            System.out.println(new String(HDFSUtil.readRemote(f.getPath())));
            System.out.println();
            System.out.println();
        }
    }
}
